#!pip install tapi-yandex-metrika
#!pip install wordcloud
import pandas as pd
import numpy as np
import scipy
from pandas.io.json import json_normalize
import json
from tapi_yandex_metrika import YandexMetrikaStats
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import smtplib
from email.mime.base import MIMEBase
from email import encoders
from datetime import date
import datetime
import os
from datetime import timedelta
import math
from win32com.client import Dispatch
import logging
import traceback
import sys
###
import matplotlib.pyplot as plt
import plotly.express as px
import seaborn as sns
from wordcloud import WordCloud, STOPWORDS
from IPython.core.display import display, HTML
import plotly.graph_objects as go
from plotly.subplots import make_subplots
Указываем счетчик из открытого источника данных - Yandex.Metrica Demo.
Номер счетчика - 44147844
ACCESS_TOKEN = # Здесь нужно указать свой токен
METRIC_IDS = "44147844"
api = YandexMetrikaStats(
access_token=ACCESS_TOKEN)
Указываем дату. Разницу timedelta = 30 days я взял вместо явного указания одного месяца, так как в январе-феврале и феврале-марте результирующая разница в данных может сильно отличаться из-за 3 недостающих дней
# укажем диапазон через datetime
End_Second_Range_Date = date.today().replace(day=1) - timedelta(days=1)
Start_Second_Range_Date = End_Second_Range_Date - timedelta(days=30)
End_First_Range_Date = Start_Second_Range_Date - timedelta(days=1)
Start_First_Range_Date = End_First_Range_Date - timedelta(days=30)
# Переведем в string и поменяем / на -
Start_Second_Range_Date = Start_Second_Range_Date.strftime('%Y/%m/%d').replace("/","-")
End_Second_Range_Date = End_Second_Range_Date.strftime('%Y/%m/%d').replace("/","-")
Start_First_Range_Date = Start_First_Range_Date.strftime('%Y/%m/%d').replace("/","-")
End_First_Range_Date = End_First_Range_Date.strftime('%Y/%m/%d').replace("/","-")
display(HTML(f"<div style='text-align:center'>{'Начало и конец 1-го временного интервала:'}</div>"),
HTML(f"<div style='text-align:center'>{Start_First_Range_Date}</div><div style='text-align:center'>{End_First_Range_Date}</div>"))
display(HTML(f"<div style='text-align:center'>{'Начало и конец 2-го временного интервала:'}</div>"),
HTML(f"<div style='text-align:center'>{Start_Second_Range_Date}<div style='text-align:center'>{End_Second_Range_Date}</div>"))
Проверим подключение к публичным данным на примере выполнения простой задачи:
Для дальнейшего обращения к Яндекс.Метрике через API предварительно нужно записать параметры в переменную. По этим параметрам запроса Яндекс.Метрика отдаст необходимый нам срез данных. Это может быть, например, количество сессий за прошлую неделю на главной странице или количество самых разных поисковых запросов перед посещением искомого сайта.
Создадим функцию request, аргументами которой будут выступать интересующие нас временные интервалы и параметры самого запроса.
def request(Start_First_Date, End_First_Date, Start_Second_Date, End_Second_Date, metrics_params, dimensions_params, filters_params):
# Укажем параметры для первого временного окна
range_params_first = dict(
ids=METRIC_IDS,
metrics = metrics_params,
dimensions = dimensions_params,
filters = filters_params,
date1 = Start_First_Date,
date2 = End_First_Date,
accuracy="full",
limit = 100000,
offset=1,
pretty=True
)
result_first = api.stats().get(params=range_params_first) # Записываем в переменную result_first данные, полученные за первый временной интервал
# Укажем параметры для второго временного окна
range_params_second = dict(
ids=METRIC_IDS,
metrics = metrics_params,
dimensions = dimensions_params,
filters = filters_params,
date1 = Start_Second_Date,
date2 = End_Second_Date,
accuracy="full",
limit = 100000,
offset=1,
pretty=True
)
result_second = api.stats().get(params=range_params_second) # Записываем в переменную result_second данные, полученные за второй временной интервал
return result_first(), result_second()
Аргументы функции request:
По итогу выполнения такой функции мы совершаем два запроса с разными временными промежутками, но одинаковыми параметрами. Таким образом меняя параметры, мы можем записывать два результата в переменные и сравнивать эти показатели. В нашем случае нам нужно разделение по операционным системам - ym:s:operatingSystem, а метрикой будет выступать количество сессий - ym:s:visits.
metrics = "ym:s:visits"
dimensions = 'ym:s:operatingSystem'
filters = ""
first_request, second_request = request('2022-10-31', '2022-11-30', '2022-12-01', '2022-12-31',"ym:s:visits", 'ym:s:operatingSystem', '')
# ИЛИ так как мы ранее обьявляли переменные с временем, то можно предыдущую строку перезаписать, как:
first_request, second_request = request(Start_First_Range_Date, End_First_Range_Date, Start_Second_Range_Date, End_Second_Range_Date,metrics, dimensions, filters)
Но пока эти результаты в очень сыром виде, их нужно обработать, напишем для этого функцию processing, которая будет обрабатывать наши requests и на выходе отдавать обработанные данные:
def processing(group_name, first_request, second_request):
# 1. Приведем полученные данные в датафреймы
data_first = pd.json_normalize(first_request.data['data'])
data_second = pd.json_normalize(second_request.data['data'])
# 2. Уберем квадратные скобки у данных, а также раскроем JSON формат данных разбив вложенные данные на несколько столбцов
clear_data_first = pd.concat([data_first.explode(["dimensions","metrics"])["metrics"].
reset_index(drop=True), pd.json_normalize(data_first.explode(["dimensions","metrics"])
["dimensions"])], axis=1).rename({'metrics':'first_metric'}, axis = 1)
clear_data_second = pd.concat([data_second.explode(["dimensions","metrics"])["metrics"].
reset_index(drop=True), pd.json_normalize(data_second.explode(["dimensions","metrics"])
["dimensions"])], axis=1).rename({'metrics':'second_metric'}, axis = 1)
# 3. Обьединим два датасета в один набор и удалим ненужные столбцы
df_helper = pd.merge(clear_data_first,clear_data_second,how='outer', left_on = 'name', right_on = 'name')\
.rename({'first_metric':'Метрика за первый временной интервал','name':group_name,
'second_metric':'Метрика за второй временной интервал'},axis=1)
# 4. Поменяем порядок столбцов для более легкого визуального восприятия, а также удалим строки с пустыми данными
df_task = df_helper.reindex(columns=[group_name,'Метрика за первый временной интервал','Метрика за второй временной интервал',])
# 5. Удалим пустые и 0 значения.
df_task= df_task.loc[(df_task['Метрика за первый временной интервал'] != 0) & (df_task['Метрика за первый временной интервал'] != 0)].dropna()
# 6. Добавим столбец с количественной разницей в сессиях
df_task['Количественная разница'] = df_task['Метрика за второй временной интервал'] - df_task['Метрика за первый временной интервал']
# 7. Добавим столбец с процентной разницей
list_columns = df_task.columns.tolist()[1:] # здесь 1:, потому что берем все кроме 1-го столбца, он в string
try: # Если метрика не целочисленная, то возникнет ошибка, поэтому добавим конструкцию с try-except
for i in list_columns:
df_task[i] = df_task[i].astype('Int32')
except TypeError:
for i in list_columns:
df_task[i] = df_task[i].astype('Float64')
df_task['Процентная разница'] = (round(((df_task['Метрика за второй временной интервал'] / df_task['Метрика за первый временной интервал'])-1)*100,2))
df_task.head(10)
return(df_task)
Аргументы функции processing:
Выполним функцию processing и запишем в переменную df_first_task
df_first_task = processing('Операционная система', first_request, second_request)
df_first_task.head(5)
| Операционная система | Метрика за первый временной интервал | Метрика за второй временной интервал | Количественная разница | Процентная разница | |
|---|---|---|---|---|---|
| 0 | Windows 10 (и последующие) | 13032 | 10283 | -2749 | -21.09 |
| 1 | macOS Catalina (и последующие) | 3072 | 2765 | -307 | -9.99 |
| 2 | Windows 7 or 2008 Server | 1342 | 1014 | -328 | -24.44 |
| 3 | Android 11 | 931 | 1374 | 443 | 47.58 |
| 4 | Android 12 | 700 | 1084 | 384 | 54.86 |
Из таблицы выше мы наблюдаем 5 самых популярных операционных систем:
# Сформируем на основе вышеприведенной таблицы две других
display(HTML(f"<div style='text-align:center'>{'Таблица с операционными системами, у которых наблюдался самый большой процентный прирост по количеству сессий:'}</div>"))
display(df_first_task.sort_values(by='Процентная разница', ascending=False).head(5).reset_index().drop('index', axis=1))
display(HTML(f"<div style='text-align:center'>{'И аналогичная таблица с операционными системами, у которых наблюдался самый большой процентный спад по количеству сессий:'}</div>"))
display(df_first_task.sort_values(by='Процентная разница').head(5).reset_index().drop('index', axis=1))
| Операционная система | Метрика за первый временной интервал | Метрика за второй временной интервал | Количественная разница | Процентная разница | |
|---|---|---|---|---|---|
| 0 | Android 13 | 34 | 113 | 79 | 232.35 |
| 1 | Windows XP | 5 | 16 | 11 | 220.0 |
| 2 | iOS 11 | 7 | 15 | 8 | 114.29 |
| 3 | Google Android 7.1 Nougat | 29 | 53 | 24 | 82.76 |
| 4 | Google Android 8.1 Oreo | 113 | 206 | 93 | 82.3 |
| Операционная система | Метрика за первый временной интервал | Метрика за второй временной интервал | Количественная разница | Процентная разница | |
|---|---|---|---|---|---|
| 0 | OS X Yosemite | 12 | 1 | -11 | -91.67 |
| 1 | Windows Mobile (другие или не определено) | 8 | 1 | -7 | -87.5 |
| 2 | Mac OS (другие) | 15 | 5 | -10 | -66.67 |
| 3 | OS X Mavericks | 2 | 1 | -1 | -50.0 |
| 4 | Google Android 5.0 Lollipop | 8 | 4 | -4 | -50.0 |
Как видно из полученной таблицы выше, самый большой положительный прирост по сессиям, выраженный в процентном соотношении, был у таких операционных систем, как:
Построим столбчатые диаграммы, в которых проследим разницу популярности разных операционных систем за два разных временных промежутка:
# Дублируем строки и в продублированных выставляем флаг 'True'
first_task_barplot = pd.concat([df_first_task] * 2).sort_index().reset_index(drop=True)
first_task_barplot['Временной интервал'] = first_task_barplot.duplicated().astype(str)
# Заменяем 'False' на 'Первый временной интервал', а True' на 'Второй временной интервал'
first_task_barplot['Временной интервал'] = first_task_barplot.replace('False','Первый временной интервал').\
replace('True','Второй временной интервал')['Временной интервал']
# Склеиваем два датафрейма, одновременно с этим переименовывая столбцы
first_task_barplot = pd.concat([first_task_barplot.loc[first_task_barplot['Временной интервал'] == 'Первый временной интервал']
[['Операционная система','Метрика за первый временной интервал', 'Временной интервал']]
.rename({'Метрика за первый временной интервал':'Метрика'}, axis = 1),
first_task_barplot.loc[first_task_barplot['Временной интервал'] == 'Второй временной интервал']
[['Операционная система','Метрика за второй временной интервал', 'Временной интервал']]
.rename({'Метрика за второй временной интервал':'Метрика'}, axis = 1)])
# Строим столбчатые диаграммы
fig = px.bar(first_task_barplot,
x="Временной интервал",
y="Метрика",
color="Операционная система",
text="Операционная система",
width=1000, height=670, opacity = 0.75)
fig.show()
https://metrica.yandex.com/aboutЭто не решить уже «в лоб» без применения фильтрации (filters). Фильтрацию организуем через регулярное выражение и запись вида =~'regular_expression'
metrics = "ym:s:visits"
dimensions = 'ym:s:startURL'
filters = "EXISTS(ym:s:startURL=~'^https://metrica.yandex.com/about/..*')" # Этим регулярным выражением мы откидываем одну
# страницу, но можно написать и по другому, составив список исключений через NONE
first_request, second_request = request(Start_First_Range_Date, End_First_Range_Date, Start_Second_Range_Date, End_Second_Range_Date, metrics, dimensions, filters)
df_second_task = processing('URL сайта', first_request, second_request)
df_second_task.head(10)
| URL сайта | Метрика за первый временной интервал | Метрика за второй временной интервал | Количественная разница | Процентная разница | |
|---|---|---|---|---|---|
| 0 | https://metrica.yandex.com/about/info/privacy-... | 1376 | 2757 | 1381 | 100.36 |
| 1 | https://metrica.yandex.com/about/info/integrat... | 387 | 109 | -278 | -71.83 |
| 2 | https://metrica.yandex.com/about/info/pricing | 310 | 247 | -63 | -20.32 |
| 3 | https://metrica.yandex.com/about/info/traffic | 102 | 101 | -1 | -0.98 |
| 4 | https://metrica.yandex.com/about/info/features | 94 | 81 | -13 | -13.83 |
| 5 | https://metrica.yandex.com/about/info/data-pol... | 82 | 81 | -1 | -1.22 |
| 6 | https://metrica.yandex.com/about/info/behavior | 67 | 62 | -5 | -7.46 |
| 7 | https://metrica.yandex.com/about/info/audience | 64 | 46 | -18 | -28.12 |
| 8 | https://metrica.yandex.com/about/info/gdpr | 44 | 46 | 2 | 4.55 |
| 9 | https://metrica.yandex.com/about/info/performance | 43 | 51 | 8 | 18.6 |
Мы разделим на 3 группы - низкочастотные, среднечастотные, высокочастотные запросы. Они различаются между собой по частоте обращения к поисковым системам за выбранный промежуток времени.
Низкочастотные запросы (НЧ) - это наименее запрашиваемые поисковые запросы за выбранный промежуток времени. Как правило, это редкий и уникальный запрос. Например, яндекс метрика вход в личный кабинет мои компании.
Среднечастотный трафик (СЧ) - это запросы в поисковой системе, по которым зафиксировано среднее количество обращений от пользователей за выбранный промежуток времени. Выглядят как НЧ запросы, но имеют более общую формулировку, например, вход в яндекс метрику.
Высокочастотный трафик (ВЧ) - это наиболее запрашиваемые поисковые запросы за выбранный промежуток времени. Такой запрос обычно формулируется в общем, например, яндекс метрика.
Верхняя и нижняя границы этих групп очень условные и зависят от множества факторов. Я буду отталкиваться от такого распределения:
metrics = "ym:s:visits"
dimensions = 'ym:s:lastSearchPhrase'
filters = "(EXISTS(ym:s:<attribution>SearchPhrase=*'*яндекс*')\
OR EXISTS(ym:s:<attribution>SearchPhrase=*'*метрика*')\
OR EXISTS(ym:s:<attribution>SearchPhrase=*'*yandex*')\
OR EXISTS(ym:s:<attribution>SearchPhrase=*'*metrika*'))"
first_request, second_request = request(Start_First_Range_Date, End_First_Range_Date, Start_Second_Range_Date, End_Second_Range_Date, metrics, dimensions, filters)
Можно переписать параметр filters из ячейки выше нижеизложенным вариантом, как раз отрабатывая фильтрацию вместе с параметром NONE.
В таком случае мы избавимся от поисковых запросов, включающих в себя слова Яндекс и Yandex
filters="(EXISTS(ym:s:<attribution>SearchPhrase=*'*яндекс*')\
OR EXISTS(ym:s:<attribution>SearchPhrase=*'*метрика*')\
OR EXISTS(ym:s:<attribution>SearchPhrase=*'*yandex*')\
OR EXISTS(ym:s:<attribution>SearchPhrase=*'*metrika*'))\
AND \
(NONE(ym:s:<attribution>SearchPhrase=*'*яндекс*') AND NONE(ym:s:<attribution>SearchPhrase=*'*yandex*'))"
df_third_task = processing('Поисковый запрос', first_request, second_request)
df_third_task
| Поисковый запрос | Метрика за первый временной интервал | Метрика за второй временной интервал | Количественная разница | Процентная разница | |
|---|---|---|---|---|---|
| 0 | яндекс метрика | 4633 | 3288 | -1345 | -29.03 |
| 1 | метрика | 2097 | 1649 | -448 | -21.36 |
| 2 | метрика яндекс | 1194 | 1115 | -79 | -6.62 |
| 3 | yandex metrika | 188 | 161 | -27 | -14.36 |
| 4 | metrika.yandex.ru | 96 | 111 | 15 | 15.62 |
| ... | ... | ... | ... | ... | ... |
| 257 | яндекс метрики вход в личный кабинет | 1 | 1 | 0 | 0.0 |
| 264 | яндекс статистика сайта метрика | 1 | 1 | 0 | 0.0 |
| 267 | яндекс счётчик | 1 | 1 | 0 | 0.0 |
| 268 | яндекс. метрика | 1 | 4 | 3 | 300.0 |
| 277 | янжекс метрика | 1 | 1 | 0 | 0.0 |
125 rows × 5 columns
Разделим полученную таблицу выше на три группы по описанным выше критериям:
display(HTML(f"<div style='text-align:center'>{'Таблица с НЧ запросами'}</div>"))
display(df_third_task.loc[(df_third_task['Метрика за первый временной интервал'] < 50) | (df_third_task['Метрика за второй временной интервал'] < 50)])
display(HTML(f"<div style='text-align:center'>{'Таблица с СЧ запросами'}</div>"))
display(df_third_task.loc[((df_third_task['Метрика за первый временной интервал'] > 50) &
(df_third_task['Метрика за первый временной интервал'] < 500)) |
((df_third_task['Метрика за второй временной интервал'] > 50) &
(df_third_task['Метрика за второй временной интервал'] < 500))])
display(HTML(f"<div style='text-align:center'>{'Таблица с ВЧ запросами'}</div>"))
display(df_third_task.loc[(df_third_task['Метрика за первый временной интервал'] > 500) |
(df_third_task['Метрика за второй временной интервал'] > 500)])
| Поисковый запрос | Метрика за первый временной интервал | Метрика за второй временной интервал | Количественная разница | Процентная разница | |
|---|---|---|---|---|---|
| 11 | metrika | 46 | 37 | -9 | -19.57 |
| 12 | metrika yandex | 46 | 50 | 4 | 8.7 |
| 13 | яндекс метрика вход в личный кабинет | 32 | 40 | 8 | 25.0 |
| 14 | yandex.metrika | 24 | 20 | -4 | -16.67 |
| 15 | метрика яндекс метрика | 21 | 6 | -15 | -71.43 |
| ... | ... | ... | ... | ... | ... |
| 257 | яндекс метрики вход в личный кабинет | 1 | 1 | 0 | 0.0 |
| 264 | яндекс статистика сайта метрика | 1 | 1 | 0 | 0.0 |
| 267 | яндекс счётчик | 1 | 1 | 0 | 0.0 |
| 268 | яндекс. метрика | 1 | 4 | 3 | 300.0 |
| 277 | янжекс метрика | 1 | 1 | 0 | 0.0 |
114 rows × 5 columns
| Поисковый запрос | Метрика за первый временной интервал | Метрика за второй временной интервал | Количественная разница | Процентная разница | |
|---|---|---|---|---|---|
| 3 | yandex metrika | 188 | 161 | -27 | -14.36 |
| 4 | metrika.yandex.ru | 96 | 111 | 15 | 15.62 |
| 5 | яндекс.метрика | 87 | 55 | -32 | -36.78 |
| 6 | metrika yandex ru | 86 | 80 | -6 | -6.98 |
| 7 | яндекс метрики | 79 | 68 | -11 | -13.92 |
| 8 | yandex метрика | 66 | 50 | -16 | -24.24 |
| 9 | metrica.yandex.com | 57 | 59 | 2 | 3.51 |
| 10 | я метрика | 52 | 58 | 6 | 11.54 |
| Поисковый запрос | Метрика за первый временной интервал | Метрика за второй временной интервал | Количественная разница | Процентная разница | |
|---|---|---|---|---|---|
| 0 | яндекс метрика | 4633 | 3288 | -1345 | -29.03 |
| 1 | метрика | 2097 | 1649 | -448 | -21.36 |
| 2 | метрика яндекс | 1194 | 1115 | -79 | -6.62 |
df_third_task['Поисковый запрос'].str.split().explode().value_counts().to_frame().rename({'Поисковый запрос':'Ключевые слова'},axis=1).head(3)
| Ключевые слова | |
|---|---|
| яндекс | 65 |
| метрика | 53 |
| yandex | 17 |
Как видно из таблицы выше, три самых часто встречаемых ключевых слов в запросах - это:
яндексметрикаyandexПостроим облако слов:
# Сделаем разметку графиков в центре
HTML("""
<style>
.output_png {
display: table-cell;
text-align: center;
vertical-align: middle;
}
</style>
""")
# Меняем цвет облака в двухцветный формат
def double_color_func(word, font_size, position,orientation,random_state=None, **kwargs):
return("hsl(0,100%, 1%)")
display(HTML(f"<div style='text-align:center'>{'Количество уникальных слов для облака:'}</div>"), HTML(f"<div style='text-align:center'>{len(set(df_third_task['Поисковый запрос'].str.cat(sep=' ').split(' ')))}</div>"))
# Сменим цвет фона на белый, максимальное количество слов на 85, высоту и ширину к 3000 и 2000 соответственно
wordcloud = WordCloud( background_color="gold", width=2000, height=2000, max_words=85).generate(df_third_task['Поисковый запрос'].str.cat(sep=' '))
# Сделаем цвет букв черным
wordcloud.recolor(color_func = double_color_func)
# Установим размер облака
plt.figure(figsize=[15,10])
# Строим облако
plt.imshow(wordcloud, interpolation="bilinear")
# Убираем координатные оси
plt.axis("off")
# Убираем комментарии
plt.show()
# Обьявим временный пустой датафрейм, в который будем помещать данные с двумя метриками последовательно
df_temp = pd.DataFrame()
# Через цикл вызываем ф-ию два раза и затем склеиваем два полученных датафрейма
for metrics in ("ym:s:pageviews",'ym:s:visits'):
dimensions = 'ym:s:startURL'
filters = "EXISTS(ym:s:startURL=~'^https://metrica.yandex.com/about*')"
first_request, second_request = request(Start_First_Range_Date, End_First_Range_Date, Start_Second_Range_Date, End_Second_Range_Date,metrics, dimensions, filters)
df_temp = pd.concat([df_temp,processing(metrics, first_request, second_request)], axis=1)
# Обьединим датафрейм с самим собой по столбцам сессий и показа страниц
df_fourth_task = pd.merge(df_temp, df_temp, how='outer', left_on = 'ym:s:pageviews', right_on = 'ym:s:visits')
# Нас интересует в полученном датафрейме только часть столбцов, поэтому уберем ненужные, заодно переименовав столбцы в приемлимый вариант
df_fourth_task = pd.concat([df_fourth_task.iloc[:,0:5],df_fourth_task.iloc[:,15:20]],axis=1).dropna().rename({'ym:s:pageviews_x':'URL',
'Метрика за первый временной интервал_x':'Суммарная глубина просмотра за первый временной интервал',
'Метрика за второй временной интервал_x':'Суммарная глубина просмотра за второй временной интервал',
'Количественная разница_x':'Количественная разница суммарной глубины просмотра',
'Процентная разница_x':'Процентная разница суммарной глубины просмотра',
'Метрика за первый временной интервал_y':'Число сессий за первый временной интервал',
'Метрика за второй временной интервал_y':'Число сессий за второй временной интервал',
'Количественная разница_y':'Количественная разница числа сессий',
'Процентная разница_y':'Процентная разница числа сессий'}, axis=1).drop('ym:s:visits_y',axis=1)
# Поменяем порядок столбцов в датафрейме
df_fourth_task = df_fourth_task.reindex(columns=['URL','Суммарная глубина просмотра за первый временной интервал',
'Суммарная глубина просмотра за второй временной интервал',
'Число сессий за первый временной интервал',
'Число сессий за второй временной интервал',
'Количественная разница суммарной глубины просмотра',
'Количественная разница числа сессий',
'Процентная разница суммарной глубины просмотра',
'Процентная разница числа сессий'])
# Добавим два столбца, в которым посчитаем среднее количество просмотренных страниц за сессию
df_fourth_task['Средняя глубина просмотра за первый временной интервал'] = round(df_fourth_task['Суммарная глубина просмотра за первый временной интервал'] / df_fourth_task['Число сессий за первый временной интервал'],2)
df_fourth_task['Средняя глубина просмотра за второй временной интервал'] = round(df_fourth_task['Суммарная глубина просмотра за второй временной интервал'] / df_fourth_task['Число сессий за второй временной интервал'],2)
df_fourth_task.head(3)
| URL | Суммарная глубина просмотра за первый временной интервал | Суммарная глубина просмотра за второй временной интервал | Число сессий за первый временной интервал | Число сессий за второй временной интервал | Количественная разница суммарной глубины просмотра | Количественная разница числа сессий | Процентная разница суммарной глубины просмотра | Процентная разница числа сессий | Средняя глубина просмотра за первый временной интервал | Средняя глубина просмотра за второй временной интервал | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | https://metrica.yandex.com/about | 27979 | 23616 | 19001 | 15752 | -4363 | -3249 | -15.59 | -17.1 | 1.47 | 1.5 |
| 1 | https://metrica.yandex.com/about/info/privacy-... | 1609 | 3073 | 1376 | 2757 | 1464 | 1381 | 90.99 | 100.36 | 1.17 | 1.11 |
| 2 | https://metrica.yandex.com/about/info/integrat... | 653 | 192 | 387 | 109 | -461 | -278 | -70.6 | -71.83 | 1.69 | 1.76 |
Построим гистаграмму и посмотрим на полученное распределение средней глубины просмотра:
sns.set_style("white")
plt.figure(figsize=(15,8))
ax = sns.kdeplot(data=df_fourth_task, x="Средняя глубина просмотра за первый временной интервал", color='orange')
sns.kdeplot(data=df_fourth_task, x="Средняя глубина просмотра за второй временной интервал", color='black', ax=ax)
plt.xlabel("Средняя глубина просмотра")
plt.ylabel("Частота")
plt.title("Гистограмма средней глубины просмотра")
plt.legend(['Первый временной интервал','Второй временной интервал'])
plt.show()
Несмотря на то, что визуально оба распределения средней глубины просмотра частично похожи на нормальные, оба таковыми не являются. Докажем это по критерию Шапиро-Уилка:
for i in ['Средняя глубина просмотра за первый временной интервал','Средняя глубина просмотра за второй временной интервал']:
stat, p = scipy.stats.shapiro(df_fourth_task.loc[:,i])
print(df_fourth_task.loc[:, i].name)
display(HTML(f"<div style='text-align:center'>{'Значение p-value = %.3f' % (p)}</div>"))
alpha = 0.05
if p > alpha:
print('Отклоняем гипотезу о нормальности распределения')
else:
print('Отклоняем гипотезу о нормальности распределения\n')
Средняя глубина просмотра за первый временной интервал
Отклоняем гипотезу о нормальности распределения Средняя глубина просмотра за второй временной интервал
Отклоняем гипотезу о нормальности распределения
Из датафрейма df_fourth_task сформируем другой датафрейм df_fourth_task_end, в котором разграничение метрик по временным интервалам будет выполняться не по столбцам, а по строкам. Флаг указывающий на то, какой именно временной интервал записан в любой строке будет храниться в отдельном столбце Временной интервал.
Нужно это для того, чтобы можно было построить несколько графиков на одном, разграничив их через такие параметры, как symbol, color и т.д.
# Дублируем строки и в продублированных выставляем флаг 'True'
df_fourth_task_end = pd.concat([df_fourth_task] * 2).sort_index().reset_index(drop=True)
df_fourth_task_end['Временной интервал'] = df_fourth_task_end.duplicated().astype(str)
# Заменяем 'False' на 'Первый временной интервал', а True' на 'Второй временной интервал'
df_fourth_task_end['Временной интервал'] = df_fourth_task_end.replace('False','Первый временной интервал').replace('True','Второй временной интервал')['Временной интервал']
# Склеиваем два датафрейма, одновременно с этим переименовывая столбцы
df_fourth_task_end = pd.concat([df_fourth_task_end.loc[df_fourth_task_end['Временной интервал'] == 'Первый временной интервал'][['URL','Суммарная глубина просмотра за первый временной интервал', 'Число сессий за первый временной интервал','Временной интервал', 'Средняя глубина просмотра за первый временной интервал']].rename({'Суммарная глубина просмотра за первый временной интервал':'Суммарная глубина просмотра','Число сессий за первый временной интервал':'Число сессий', 'Средняя глубина просмотра за первый временной интервал':'Средняя глубина просмотра'},axis=1), df_fourth_task_end.loc[df_fourth_task_end['Временной интервал'] == 'Второй временной интервал'][['URL','Суммарная глубина просмотра за второй временной интервал', 'Число сессий за второй временной интервал', 'Временной интервал', 'Средняя глубина просмотра за второй временной интервал']].rename({'Суммарная глубина просмотра за второй временной интервал':'Суммарная глубина просмотра','Число сессий за второй временной интервал':'Число сессий', 'Средняя глубина просмотра за второй временной интервал':'Средняя глубина просмотра'},axis=1)]).sort_index()
Построим два графика глубины просмотра за два временных интервала, чтобы проследить зависимость между количеством визитов и глубиной просмотра по разным URL. Также не забудем перевести масштаб координатных осей в логарифмический, чтобы сохранить равнозначный масштаб для всех значений Добавим в каждый из них по три прямые линии:
# Строим диаграмму и логарифмируем координатные оси
fig = px.scatter(df_fourth_task_end, y="Суммарная глубина просмотра", color='Временной интервал',hover_data =['Средняя глубина просмотра'],
x="Число сессий", hover_name = 'URL',
log_x = True,log_y=True,
labels={"Число сессий": "Суммарное число сессий"},
title="График глубины просмотра за первый временной интервал",
color_continuous_scale=px.colors.qualitative.Pastel)
# Изменим стиль точек
fig.update_traces(marker=dict(size=8,symbol="square",
line=dict(width=0.5,
color='black')),
selector=dict(mode='markers'))
# Добавим три прямые линии
fig.add_trace(
go.Scatter(x=[1, 30000],
y=[1, 30000],
mode="lines",
line=go.scatter.Line(color="darkgreen"),
showlegend=False),
row=1,
col=1,
)
fig.add_trace(
go.Scatter(x=[1, 30000],
y=[1.5, 45000],
mode="lines",
line=go.scatter.Line(color="orchid"),
showlegend=False),
row=1,
col=1,
)
fig.add_trace(
go.Scatter(x=[1, 30000],
y=[2, 60000],
mode="lines",
line=go.scatter.Line(color="coral"),
showlegend=False),
row=1,
col=1,
)
# Сменим цвет фона и осей абсцисс, ординат
fig.update_layout(
plot_bgcolor='aliceblue'
)
fig.update_xaxes(
mirror=True,
ticks='outside',
showline=True,
linecolor='black',
gridcolor='lightgrey'
)
fig.update_yaxes(
mirror=True,
ticks='outside',
showline=True,
linecolor='black',
gridcolor='lightgrey'
)
fig.update_layout(autosize=False, height=500, width=1000)
fig.show()
Как видно из графика два разных временных интервала мало в целом чем отличаются. Большая часть значений средней глубины просмотра распределилась в районе от 1 до 2. Значения, располагающиеся у начала координат имеют заметно большее среднюю глубину просмотра. Вероятно, это связано с тем, что это низкочастотный трафик. Такой трафик намного более узкоспециализированный и такая аудитория намного больше заинтересована в изучении конечного продукта.
Можно также для более простого понимания построить 3D диаграмму, а в качестве третьей оси выбрать среднюю глубину просмотра, которую мы высчитали ранее:
fig = px.scatter_3d(df_fourth_task_end, hover_name = 'URL',
x='Суммарная глубина просмотра',
y='Число сессий',
z='Средняя глубина просмотра',
log_x=True,
log_y=True,
log_z=True,
symbol='Временной интервал',
color ='Средняя глубина просмотра',
labels={"Число сессий": "Суммарное число сессий"},
title="График глубины просмотра, 3D", opacity = 0.90, color_continuous_scale=px.colors.qualitative.Pastel)
fig.update_traces(marker=dict(size=10))
fig.update_layout(autosize=False, height=700, width=1000, coloraxis_colorbar_x=-0.15)
fig.show()